ST 306 Final Project - Group 1

In [60]:
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
In [61]:
import matplotlib.pyplot as plt
import warnings
import seaborn as sns

plt.style.use('seaborn')
sns.set_palette('cubehelix')

plt.rcParams['figure.figsize'] = [8, 4.5]
plt.rcParams['figure.dpi'] = 300
warnings.simplefilter(action='ignore', category=FutureWarning)

A. Data Preparation

Install packages

In [62]:
import yfinance as yf
import numpy as np
import pandas as pd
import pyfolio as pf

Set up parameters

In [63]:
RISKY_ASSETS = ['AAPL', 'TM', 'MCD', 'MS', 'WMT']
START_DATE = '2014-12-31'
END_DATE = '2019-12-31'

n_assets = len(RISKY_ASSETS)

Download the stock prices from Yahoo Finance

In [64]:
prices_df = yf.download(RISKY_ASSETS, 
                        start = START_DATE, 
                        end = END_DATE, 
                        adjusted = True)
print(f'Downloaded {prices_df.shape[0]} rows of data.')
prices_df['Adj Close'].plot(title = 'Stock prices of the Considered 5 Assets')
[*********************100%***********************]  5 of 5 completed
Downloaded 1259 rows of data.
Out[64]:
<matplotlib.axes._subplots.AxesSubplot at 0x121bc6130>

Calculate the simple returns

In [65]:
returns = prices_df['Adj Close'].pct_change().dropna()

B. Evaluating the performance of a basic 1/n portfolio

Define the weights

In [66]:
portfolio_weights = n_assets * [1 / n_assets]

Calculate portfolio returns:

In [67]:
portfolio_returns = pd.Series(np.dot(portfolio_weights, returns.T), 
                              index = returns.index)

Create the tear sheet (simple variant):

In [68]:
pf.create_simple_tear_sheet(portfolio_returns)
Start date2014-12-31
End date2019-12-30
Total months59
Backtest
Annual return 14.2%
Cumulative returns 94.2%
Annual volatility 14.6%
Sharpe ratio 0.99
Calmar ratio 0.74
Stability 0.88
Max drawdown -19.3%
Omega ratio 1.19
Sortino ratio 1.42
Skew -0.23
Kurtosis 2.74
Tail ratio 1.00
Daily value at risk -1.8%

C. Efficient Frontier via Monte Carlo simulations

Calculate annualized average returns and their standard deviations

In [69]:
N_PORTFOLIOS = 10 ** 5
N_DAYS = 1752

returns_df = prices_df['Adj Close'].pct_change().dropna()

avg_returns = returns_df.mean() * N_DAYS
cov_mat = returns_df.cov() * N_DAYS
In [70]:
returns_df.plot(title = 'Daily returns of the considered assets');

Simulate random portfolio weights

In [71]:
np.random.seed(42)
weights = np.random.random(size=(N_PORTFOLIOS, n_assets))
weights /=  np.sum(weights, axis=1)[:, np.newaxis]

Calculate portfolio metrics

In [72]:
portf_rtns = np.dot(weights, avg_returns)

portf_vol = []
for i in range(0, len(weights)):
    portf_vol.append(np.sqrt(np.dot(weights[i].T, 
                                    np.dot(cov_mat, weights[i]))))
portf_vol = np.array(portf_vol)  
portf_sharpe_ratio = portf_rtns / portf_vol

Create a joint DataFrame with all data

In [73]:
portf_results_df = pd.DataFrame({'returns': portf_rtns,
                                 'volatility': portf_vol,
                                 'sharpe_ratio': portf_sharpe_ratio})

Locate the points creating the Efficient Frontier

In [74]:
N_POINTS = 100
portf_vol_ef = []
indices_to_skip = []

portf_rtns_ef = np.linspace(portf_results_df.returns.min(), 
                            portf_results_df.returns.max(), 
                            N_POINTS)
portf_rtns_ef = np.round(portf_rtns_ef, 2)    
portf_rtns = np.round(portf_rtns, 2)

for point_index in range(N_POINTS):
    if portf_rtns_ef[point_index] not in portf_rtns:
        indices_to_skip.append(point_index)
        continue
    matched_ind = np.where(portf_rtns == portf_rtns_ef[point_index])
    portf_vol_ef.append(np.min(portf_vol[matched_ind]))
    
portf_rtns_ef = np.delete(portf_rtns_ef, indices_to_skip)

Plot the Efficient Frontier

In [75]:
MARKS = ['o', 'X', 'd', '*', '1']

fig, ax = plt.subplots()
portf_results_df.plot(kind = 'scatter', x = 'volatility', 
                      y = 'returns', c = 'sharpe_ratio',
                      cmap = 'RdYlGn', edgecolors = 'black', 
                      ax = ax)
ax.set(xlabel = 'Volatility', 
       ylabel = 'Expected Returns', 
       title = 'Efficient Frontier')
ax.plot(portf_vol_ef, portf_rtns_ef, 'b--')
for asset_index in range(n_assets):
    ax.scatter(x = np.sqrt(cov_mat.iloc[asset_index, asset_index]), 
               y = avg_returns[asset_index], 
               marker = MARKS[asset_index], 
               s = 150, 
               color = 'black',
               label = RISKY_ASSETS[asset_index])
ax.legend()

plt.tight_layout()
plt.show()

Calculate the portfolio with max Sharpe Ratio and min Volatility

In [76]:
max_sharpe_ind = np.argmax(portf_results_df.sharpe_ratio)
max_sharpe_portf = portf_results_df.loc[max_sharpe_ind]

min_vol_ind = np.argmin(portf_results_df.volatility)
min_vol_portf = portf_results_df.loc[min_vol_ind]

Provide the specific proportions of each asset as maximizing Sharpe Ratio

In [77]:
print('Maximum Sharpe Ratio portfolio ----')
print('Performance')
for index, value in max_sharpe_portf.items():
    print(f'{index}: {100 * value:.2f}% ', end="", flush=True)
print('\nWeights')
for x, y in zip(RISKY_ASSETS, weights[np.argmax(portf_results_df.sharpe_ratio)]):
    print(f'{x}: {100*y:.2f}% ', end="", flush=True)
Maximum Sharpe Ratio portfolio ----
Performance
returns: 139.51% volatility: 40.38% sharpe_ratio: 345.50% 
Weights
AAPL: 32.02% TM: 62.81% MCD: 1.39% MS: 1.27% WMT: 2.51% 

Provide the specific proportions of each asset as minimizing volatility

In [78]:
print('Minimum Volatility portfolio ----')
print('Performance')
for index, value in min_vol_portf.items():
    print(f'{index}: {100 * value:.2f}% ', end = "", flush = True)
print('\nWeights')
for x, y in zip(RISKY_ASSETS, weights[np.argmin(portf_results_df.volatility)]):
    print(f'{x}: {100*y:.2f}% ', end = "", flush=True)
Minimum Volatility portfolio ----
Performance
returns: 94.94% volatility: 34.42% sharpe_ratio: 275.83% 
Weights
AAPL: 7.15% TM: 36.40% MCD: 0.03% MS: 28.83% WMT: 27.58% 

Plot the Efficient Frontier

In [79]:
fig, ax = plt.subplots()
portf_results_df.plot(kind = 'scatter', x = 'volatility', 
                      y = 'returns', c = 'sharpe_ratio',
                      cmap = 'RdYlGn', edgecolors = 'black', 
                      ax = ax)
ax.scatter(x = max_sharpe_portf.volatility, 
           y = max_sharpe_portf.returns, 
           c = 'black', marker = '*', 
           s = 200, label = 'Max Sharpe Ratio')
ax.scatter(x = min_vol_portf.volatility, 
           y = min_vol_portf.returns, 
           c = 'black', marker = 'P', 
           s = 200, label = 'Minimum Volatility')
ax.set(xlabel = 'Volatility', ylabel = 'Expected Returns', 
       title = 'Efficient Frontier')
ax.legend()

plt.tight_layout()
plt.show()